Skip to content

Add opt-in .pcpignore exclusions - #1460

Open
davidperezgar wants to merge 7 commits into
trunkfrom
codex/pcpignore
Open

Add opt-in .pcpignore exclusions#1460
davidperezgar wants to merge 7 commits into
trunkfrom
codex/pcpignore

Conversation

@davidperezgar

@davidperezgar davidperezgar commented Sep 1, 2026

Copy link
Copy Markdown
Member

Summary

  • add an opt-in .pcpignore format for local and CI file exclusions
  • add --use-pcpignore to WP-CLI and a disabled-by-default Admin Scanner option
  • keep .pcpignore inactive unless explicitly requested, so WordPress.org scans continue to inspect every distributed file
  • document the supported path format and add unit and Behat coverage

Closes #1459

Testing

  • git diff --check
  • phpcs --standard=phpcs.xml.dist for changed PHP files
  • wp-scripts lint-js assets/js/plugin-check-admin.js
  • PHP syntax checks for changed PHP files
  • Not run: PHPUnit requires local WP_TESTS_* constants.
  • Not run: the targeted Behat scenario requires downloading WordPress core and a local MySQL test database; this environment cannot resolve api.wordpress.org and has no test database.

Disclosure: This pull request was drafted with assistance from OpenAI Codex and reviewed by its author.

Open WordPress Playground Preview

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the props-bot label.

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: davidperezgar <davidperez@git.wordpress.org>
Co-authored-by: frantorres <frantorres@git.wordpress.org>
Co-authored-by: ernilambar <nilambar@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.

@ernilambar

Copy link
Copy Markdown
Member

Review details

Model: Opus 4.8

Blocking

1. "Relative to plugin root" is false — matching is unanchored.
Docs (CLI.md:186, docblock:252) claim root-relative paths. Entries actually go through unanchored substring matchers — strpos($path, '/'.$dir.'/') (Abstract_File_Check.php:301) and str_ends_with($path, "/$file") (:308). So docs/ silently skips includes/admin/docs/real-code.php, not just root docs/. For a compliance tool, silent over-exclusion hides distributed files that must be checked. Fix: anchor patterns to plugin root (or correct the docs). Behat only tests the root case, so the bug is invisible to the suite.

2. No glob support — proposal's own *.map example silently no-ops.
Only literal paths are handled. *.mapstr_ends_with($path, "/*.map") → never matches, no warning. Implement fnmatch globbing or explicitly reject/warn on wildcard lines.

3. AC unmet: no warning on invalid/unreadable file.
get_exclusions() returns empty silently on !is_readable() (PCP_Ignore_Utility.php:329) and false === $lines (:336). Issue requires a "useful warning." A typo means silent full-scan while the user believes exclusions applied.

Non-blocking

  • 5. Single-file plugin resolves .pcpignore to WP_PLUGIN_DIR root (shared/leaky). Guard the single-file case. Better to exclude for single file plugin
  • 6. Wrong phpcs annotation (PCP_Ignore_Utility.php:333) suppresses file_get_contents sniff but code uses file().

Test gaps

Covered Missing
Opt-in gating both ways (behat) Nested-path anchoring (would catch #1)
Exclusion parsing (unit) Glob behavior (#2)
Missing-file → empty (unit) Unreadable-file warning (#3)
Files excluded end-to-end; .pcpignore self-exclusion

@davidperezgar

Copy link
Copy Markdown
Member Author

Codex has reviewed this PR.

The implementation and test coverage are a solid start, and the current CI checks are passing. However, I found a few issues that should be addressed before merging:

  1. Documented root-relative paths are matched as unanchored path fragments. For example, docs/ can also exclude files under includes/docs/, which may hide distributed files unintentionally.
  1. The proposed *.map syntax is not handled consistently by file-based checks, where it is treated as a literal filename rather than a glob pattern.
  1. An unreadable .pcpignore file is ignored silently, although the acceptance criteria require a useful warning where appropriate.

There is also a non-blocking edge case for single-file plugins: the lookup falls back to WP_PLUGIN_DIR/.pcpignore rather than a plugin-specific location.

I would not approve this PR until the blocking points above are resolved.

* them at that exact location, and may include `*` / `?` wildcards.
* Unanchored entries retain the historical any-depth pattern.
*
* @since n.e.x.t

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @since n.e.x.t
* @since 2.2.0

@ernilambar

Copy link
Copy Markdown
Member

Issues

1. * and ? cross /, so wildcards are not root-anchored

includes/Utilities/Plugin_Request_Utility.php:248glob_to_regex() maps *.*, which matches /.

  • *.map#^<root>/.*\.map$#i, so assets/vendor/deep/app.js.map is excluded.
  • * or *.php silently excludes the entire plugin tree.
  • docs*/ matches documentation/anything.
  • Contradicts docs/CLI.md ("anchored to that root (not matched at any depth)").
  • The PHPUnit fixture test-plugin-pcpignore/assets/app.js.map only passes because of this bug.

Fix: use [^/]* and [^/]; keep the /* suffix as /.* for directory entries.

2. PHPCS receives raw regex, so PHPCS checks and file checks disagree

includes/Checker/Checks/Abstract_PHP_CodeSniffer_Check.php:281 — PHPCS builds '`' . strtr($pattern, ['\\,'=>',', '*'=>'.*']) . '`i' and preg_matches it: unanchored, only * translated, nothing escaped.

  • ? is not a wildcard: file?.php excludes fil.php and not file1.php — inverse of is_file_ignored().
  • Metacharacters unescaped: data[1].php, a+b.php, my(file).php match wrong paths; a backtick breaks preg_match outright.
  • Unanchored prefix match: config.php also excludes config.php.bak; docs excludes all of docs/** in PHPCS checks while is_file_ignored()'s === excludes nothing.

Same config, different exclusions per check type. Fix: quote the pattern for PHPCS and expand ? ourselves.

3. chmod 0000 tests fail when the suite runs as root

tests/phpunit/tests/Utilities/Plugin_Request_Utility_Tests.php:360 (and the matching Behat scenario).

  • Root bypasses permission bits, so is_readable() returns true, exclusions parse normally, and assertNotSame( '', get_warning() ) fails. Running PHPUnit/Behat as root in a container is a common CI setup.
  • The test chmods a git-tracked fixture, so an aborted run leaves tests/phpunit/testdata/plugins/test-plugin-pcpignore/.pcpignore at mode 0000 in the working tree.

Fix: mock the filter instead of chmodding a fixture.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add opt-in .pcpignore exclusions for local and CI scans

3 participants